package com.acerola.downloader.core;

import com.acerola.downloader.constant.Constant;
import com.acerola.downloader.util.FileUtils;
import com.acerola.downloader.util.HttpUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;
import java.net.HttpURLConnection;
import java.util.ArrayList;
import java.util.Scanner;
import java.util.concurrent.*;

/**
 * @program: MultithreadedDownloader
 * @description: 下载器
 * @author: Acerola
 * @create: 2022-05-11 09:36
 **/

public class Downloader {
    private Logger logger = LoggerFactory.getLogger(Downloader.class);
    private ScheduledExecutorService scheduledExecutorService = new ScheduledThreadPoolExecutor(2);
    private ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(Constant.THREAD_NUM, Constant.THREAD_NUM, 0, TimeUnit.SECONDS, new ArrayBlockingQueue<>(5));
    private CountDownLatch countDownLatch = new CountDownLatch(Constant.THREAD_NUM);

    public void downloader(String url) throws InterruptedException {
        //获取文件名
        String httpFileName = HttpUtils.getHttpFileName(url);
        String demo = "D:\\Download\\";
        System.out.println("即将开始下载" + httpFileName);
        System.out.println("请输入保存位置:(就像这样:" + " " + demo + ")");
        Scanner scanner = new Scanner(System.in);
        String path = scanner.next();
        //文件下载路径
        httpFileName = path + httpFileName;
        //获取本地文件大小
        long localFileLength = FileUtils.getFileContentLength(httpFileName);


        //获取链接对象
        HttpURLConnection urlConnection = null;
        DownloaderInfoThread downloaderInfoThread = null;
        try {
            urlConnection = HttpUtils.getURLConnection(url);
            //获取下载文件总大小
            int contentLength = urlConnection.getContentLength();

            /**
             * 判断文件是否已经下载过了
             */
            if (localFileLength >= contentLength) {
                logger.info("{}文件已下载,无需重新下载", httpFileName);
                return;
            }
            //创建获取下载信息的任务对象
            downloaderInfoThread = new DownloaderInfoThread(contentLength);
            //将任务交给线程执行,每隔一秒执行一次
            scheduledExecutorService.scheduleAtFixedRate(downloaderInfoThread, 1, 1, TimeUnit.SECONDS);
            //切分任务
            ArrayList<Future> list = new ArrayList<>();
            spilt(url, list, path);
            //等待拆分方法中所有线程执行完毕
            countDownLatch.await();
            //合并文件
            if (merge(httpFileName)) {
                //清除临时文件
                clearTemp(httpFileName);
            }


        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            System.out.println("\r");
            System.out.println("下载完成");
            //关闭连接对象
            if (urlConnection != null) {
                urlConnection.disconnect();
            }
            //关闭
            scheduledExecutorService.shutdown();
            //关闭线程池
            if (threadPoolExecutor != null) {
                threadPoolExecutor.shutdown();

                if (!threadPoolExecutor.awaitTermination(1, TimeUnit.MINUTES)) {
                    //等待1分钟，如果线程池没有关闭则会执行进来
                    threadPoolExecutor.shutdownNow();
                }
            }
        }
    }

    /**
     * 文件切分
     *
     * @param url
     * @param futureList
     */
    public void spilt(String url, ArrayList<Future> futureList, String path) {

        //获取下载文件大小
        long contentLength = HttpUtils.getHttpFileContentLength(url);

        //计算切分后的文件大小
        long size = contentLength / Constant.THREAD_NUM;

        //计算分块个数
        for (int i = 0; i < Constant.THREAD_NUM; i++) {
            //计算下载起始位置
            long startPos = i * size;
            //计算结束位置
            long endPos;

            if (i == Constant.THREAD_NUM - 1) {
                //下载最后一块，下载剩余的部分
                endPos = 0;
            } else {
                endPos = startPos + size;
            }

            //如果不是第一块，起始位置要+1
            if (startPos != 0) {
                startPos++;
            }

            //创建任务对象
            DownloaderTask downloaderTask = new DownloaderTask(url, startPos, endPos, i, countDownLatch, path);

            //将任务提交到线程池中
            Future<Boolean> future = threadPoolExecutor.submit(downloaderTask);

            futureList.add(future);
        }
    }

    /**
     * 文件合并
     *
     * @param fileName
     * @return
     */
    public boolean merge(String fileName) {
        logger.info("开始合并文件{}", fileName);
        byte[] buffer = new byte[Constant.BYTE_SIZE];
        int len = 0;
        try (RandomAccessFile accessFile = new RandomAccessFile(fileName, "rw")) {
            for (int i = 0; i < Constant.THREAD_NUM; i++) {
                try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(fileName + ".temp" + i))) {
                    while ((len = bis.read(buffer)) != -1) {
                        accessFile.write(buffer, 0, len);
                    }
                }
            }
            logger.info("文件合并完毕{}" + fileName);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }

        return true;
    }

    /**
     * 清除临时文件
     *
     * @param fileName
     * @return
     */
    public boolean clearTemp(String fileName) {
        for (int i = 0; i < Constant.THREAD_NUM; i++) {
            File file = new File(fileName + ".temp" + i);
            file.delete();
        }

        return true;
    }
}
